﻿using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace VoxelSystem.VoxelSystem
{
    public class VS_GridCube
    {
        private VS_VoxelSystem _root;
        
        private Vector3 _position; // cubes center
        private float _dimension;
      
        // for logic        
        private VS_GridCube _parentGridCube;
        private List<VS_GridCube> _neighbours;
        private List<VS_Voxel> _influencingVoxels;

        // for marching cubes
        private float[] _cornerWeights = new float[8];
        private int _marchingCubesCase = -1;
        private bool _mirrorX = false;
        private bool _mirrorY = false;
        private bool _mirrorZ = false; 
        private bool _inverse = false;  // (filled corners > 4) -> flip normals
        private bool _filled = false;
        private bool _empty = false;

        // results
        private List<Vector3> _edgeIntersection = new List<Vector3>();

        public VS_GridCube(VS_VoxelSystem root, Vector3 position)
        {
            _root = root;
            _position = position;
            _dimension = VS_Settings.GridDistance;

            _parentGridCube = null;
            _neighbours = new List<VS_GridCube>();      // root.GetNeighbourCubes(this)
            _influencingVoxels = new List<VS_Voxel>();
        }

        public void UpdateInfluencingVoxels()
        {
            _cornerWeights = new float[8];
            _filled = false;
            _empty = false;

            int cnt_filled = 0;
            int cnt_empty = 0;

            for (int n = 0; n < 8; n++)
            {
                if (_influencingVoxels.Count > 1)
                {
                    float closestDistance = float.MaxValue;
                    VS_Voxel closestVoxel = null;
                    foreach (VS_Voxel v in _influencingVoxels)
                    {
                        float distance = Vector3.Distance(this.Corners[n], v.Position);
                        if (distance < closestDistance)
                        {
                            closestDistance = distance;
                            closestVoxel = v;  
                        }
                    }

                    if (closestDistance <= closestVoxel.NearRange)
                    {
                        _cornerWeights[n] = 1;
                        cnt_filled++;
                    }
                    else if (closestDistance > closestVoxel.InfluenceRange)
                    {
                        _cornerWeights[n] = 0;
                        cnt_empty++;
                    }
                    else
                    {
                        _cornerWeights[n] = 1 - (closestDistance / closestVoxel.InfluenceRange);
                    }
                }
                else if(_influencingVoxels.Count == 1)
                {
                    float distance = Vector3.Distance(this.Corners[n], _influencingVoxels[0].Position);
                    if (distance <= _influencingVoxels[0].NearRange)
                    {
                        _cornerWeights[n] = 1;
                        cnt_filled++;
                    }
                    else if (distance > _influencingVoxels[0].InfluenceRange)
                    {
                        _cornerWeights[n] = 0;
                        cnt_empty++;
                    }
                    else
                    {
                        _cornerWeights[n] = 1 - (distance / _influencingVoxels[0].InfluenceRange);
                    }
                }
            }

            if (cnt_empty == 8)
                _empty = true;
            else if (cnt_filled == 8)
                _filled = true;

            if (!_empty && !_filled)
                UpdateIntersections();
        }
        public void UpdateIntersections()
        {
            // Marching Cubes Algorithm
            _edgeIntersection = new List<Vector3>();
            for (int n=0; n<12; n++)
            {
                int edge1 = VS_VoxelSystem.EdgeDefinition[n, 0];
                int edge2 = VS_VoxelSystem.EdgeDefinition[n, 1];
                if ((_cornerWeights[edge1] == 1 && _cornerWeights[edge2] != 1) || (_cornerWeights[edge1] != 1 && _cornerWeights[edge2] == 1))
                {
                    float calcDistance = 1 - ((_cornerWeights[edge1] + _cornerWeights[edge2]) / 2);
                    if (n < 4) // x
                        _edgeIntersection.Add(this.Corners[edge1] + new Vector3(calcDistance*VS_Settings.GridDistance, 0, 0));
                    else if (n < 8) // y
                        _edgeIntersection.Add(this.Corners[edge1] + new Vector3(0, calcDistance * VS_Settings.GridDistance, 0));
                    else
                        _edgeIntersection.Add(this.Corners[edge1] + new Vector3(0, 0, calcDistance * VS_Settings.GridDistance));
                    //_edgeIntersection[n] = 1 - ((_cornerWeights[edge1] + _cornerWeights[edge2]) / 2);
                }
            }

            // test:
            if (_edgeIntersection.Count == 4)
            {   // 4 kanten sind gebrochen, spanne vertex auf
                for (int i = 0; i < 4; i++)
                {
                    _root.Vertices.Add(new VertexPositionNormalColored(_edgeIntersection[i], Color.Gray));
                }
                // tri 1
                _root.VerticeIndices.Add(_root.Vertices.Count - 4);
                _root.VerticeIndices.Add(_root.Vertices.Count - 3);
                _root.VerticeIndices.Add(_root.Vertices.Count - 2);
                // tri 2
                _root.VerticeIndices.Add(_root.Vertices.Count - 3);
                _root.VerticeIndices.Add(_root.Vertices.Count - 2);
                _root.VerticeIndices.Add(_root.Vertices.Count - 1);
            }
        }
        public void Update(GameTime gameTime)
        {
            
        }

        public void AddInfluencingVoxel(VS_Voxel vox)
        { 
            //if (_influencingVoxels.FindIndex(iv => iv.Id == vox.Id) == -1)
                _influencingVoxels.Add(vox);
        }

        public bool IsVoxelInRange(VS_Voxel voxel)
        {
            return IsVoxelInRange(voxel, RangeType.NearFarRange);
        }
        public bool IsVoxelInRange(VS_Voxel voxel, RangeType rangeType)
        {
            float diffX = voxel.Position.X - _position.X;
            float diffY = voxel.Position.Y - _position.Y;
            float diffZ = voxel.Position.Z - _position.Z;

            int cornerIndex = 0;
            if (diffX < 0)
            {   // 0,4,3,7
                if (diffY < 0)
                {   // 0,4
                    if (diffZ < 0)
                        cornerIndex = 0;
                    else
                        cornerIndex = 4;
                }
                else
                {   // 3,7
                    if (diffZ < 0)
                        cornerIndex = 3;
                    else
                        cornerIndex = 7;
                }
            }
            else
            {   // 1,2,5,6
                if (diffY < 0)
                {   // 1,5
                    if (diffZ < 0)
                        cornerIndex = 1;
                    else
                        cornerIndex = 5;
                }
                else
                {   // 2,6
                    if (diffZ < 0)
                        cornerIndex = 2;
                    else
                        cornerIndex = 6;
                }
            }

            switch (rangeType)
            {
                case RangeType.NearFarRange:
                    if (Vector3.Distance(this.Corners[cornerIndex], voxel.Position) < voxel.InfluenceRange)
                        return true;
                    break;
                case RangeType.FarRange:
                    if (Vector3.Distance(this.Corners[cornerIndex], voxel.Position) < voxel.FarRange)
                        return true;
                    break;
                case RangeType.NearRange:
                    if (Vector3.Distance(this.Corners[cornerIndex], voxel.Position) < voxel.NearRange)
                        return true;
                    break;
            }
            
            return false;
        }

        public Vector3 CenterPosition
        {
            get { return _position; }
            /*set { _position = value; }*/ // if enable please recalculate all stuff
        }
        public float Dimension
        {
            get { return _dimension; }
        }
        public List<Vector3> EdgeIntersections
        {
            get { return _edgeIntersection; }
        }
        public Vector3[] Corners
        {
            get
            {
                Vector3[] vecs = new Vector3[8];
                float halfDimension = _dimension/2;

                vecs[0] = _position + new Vector3(-halfDimension, -halfDimension, -halfDimension);
                vecs[1] = _position + new Vector3(+halfDimension, -halfDimension, -halfDimension);
                vecs[2] = _position + new Vector3(+halfDimension, +halfDimension, -halfDimension);
                vecs[3] = _position + new Vector3(-halfDimension, +halfDimension, -halfDimension);

                vecs[4] = _position + new Vector3(-halfDimension, -halfDimension, +halfDimension);
                vecs[5] = _position + new Vector3(+halfDimension, -halfDimension, +halfDimension);
                vecs[6] = _position + new Vector3(+halfDimension, +halfDimension, +halfDimension);
                vecs[7] = _position + new Vector3(-halfDimension, +halfDimension, +halfDimension);

                return vecs;
            }
        }
        public float[] CornerWeights
        {
            get { return _cornerWeights; }
        }
        public List<VS_Voxel> InfluencingVoxels
        {
            get { return _influencingVoxels; }
        }
        public bool Filled
        {
            get 
            {
                foreach (float cw in _cornerWeights)
                {
                    if (cw != 1.0f)
                        return false;
                }
                return true;
            }
        }
        public bool Empty
        {
            get
            {
                foreach (float cw in _cornerWeights)
                {
                    if (cw != 0.0f)
                        return false;
                }
                return true;
            }
        }
    }
}
